8.7 关键字:explicit¶
构造函数与explicit¶
1. 拷贝构造函数不应该是explicit的¶
拷贝构造函数通常不应该是explicit的,因为它确实在一些情况下会被隐式地使用。
2. 转换构造函数应该是explicit的¶
我们可以将构造函数声明为explicit来阻止构造函数定义的隐式类类型转换,需要注意如下几点:
关键字explicit只对一个实参的构造函数有效:需要多个实参的构造函数不能用于执行隐式转换,因此无需将这些构造函数指定为explicit
只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应该重复
explicit声明的构造函数只能以直接初始化的形式使用
class Foo {
public:
// 阻止隐式类类型转换
explicit Foo(std::string s) : str(s) { }
private:
std::string str;
}
std::string str = "tomocat";
// 错误: 不支持string到Foo类型的隐式初始化
Foo = str;
// 正确: 显式初始化
Foo(str);
static_cast<Foo>(str);
类型转换运算符与explicit¶
Tips:实践中类很少提供类型转换运算符,所以类型转换运算符常被
explicit修饰以阻止隐式转换。
在实践中类很少提供类型转换运算符,在大多数情况下,如果类型转换自动发生,用户可能会感觉比较意外,而不是感觉受到了帮助。然而这条经验法则存在一种例外情况:对于类来说,定义向bool的类型转换还是比较普遍的现象。但是这种类型转换可能引发意想不到的结果,特别是当istream含有向bool的类型转换时,下面的代码仍然编译通过:
// 如果向bool的类型转换不是显式的,则该代码在编译器看来将是合法的
// 因为istream本身并没有定义<<运算符,所以本来这段代码应该产生错误
int i = 42;
cin << i;
// 执行过程如下:
// 1) istream的bool类型转换符将cin转换为bool
// 2) bool被提升为int并作为左移运算符的左侧运算对象
// 3) 提升后的bool值(1或0)会被左移42个位置
为了防止这样的异常发生,C++新标准引入了显式的类型转换运算符:
class SmallInt {
public:
// 转换构造函数: 编译支持int到SmallInt的隐式转换
SmallInt(int i = 0) : val_(i) {
if (i < 0 || i > 255)
throw std::out_of_range("Bad SmallInt value");
}
// 类类型转换运算符: 编译器不支持SmallInt到int的隐式转换
explicit operator int() const { return val_; }
// ...其他成员
private:
int val_;
};
// 正确:SmallInt的构造函数不是显式的
SmallInt si = 3;
// 错误: 此处需要隐式的类型转换,但类的运算符是显式的
si + 3;
// 正确: 显式地请求类型转换
static_cast<int>(si) + 3;